#include <stdio.h>
#include "camdrv.h"
#include "camplus.h"
#include "event.h"

#define LCD_FRAME_SIZE 240*320*CAMERA_DATA_BYTES

//static Uint16 s_tmpBuffer[240*320*2];
//static Uint16 s_tmpBuffer[144*176*2];

class CSpinLock
{
public:
    CSpinLock() : m_bLocked(FALSE) {}

    void Lock();

    void Unlock();

    _ELASTOS Boolean TryLock();

private:
    _ELASTOS Int32 m_bLocked;
};

INLINE void CSpinLock::Lock()
{
    while (InterlockedExchange(&m_bLocked, TRUE)) {
        while (m_bLocked) {
            DzSleep(DzMillisecondsToTicks(1), NULL);
        }
    }
}

INLINE void CSpinLock::Unlock()
{
    InterlockedExchange(&m_bLocked, FALSE);
}

INLINE _ELASTOS Boolean CSpinLock::TryLock()
{
    return !InterlockedExchange(&m_bLocked, TRUE);
}

static Boolean s_bStopFlag = TRUE;
static CSpinLock s_lock;

CCamVideo::CCamVideo()
{
    m_OutFramesBuffer = NULL;
    m_pFrameCopyEvent = NULL;
    m_ulRecordBufFrameLen = 0;
    m_uOutFrameID = 0;
    m_uInFrameID = 0;
    m_iWidth = 0;
    m_iHeight = 0;
    m_VideoAppBuffer[0] = NULL;
}

void CCamVideo::Destroy()
{
    FreeBuffer();
    if(NULL != m_pFrameCopyEvent){
//        kprintf("Distory m_pFrameCopyEvent:0x%x.\n", m_pFrameCopyEvent);
        m_pFrameCopyEvent->Release();
        m_pFrameCopyEvent = NULL;
    }
    m_ulRecordBufFrameLen = 0;
    m_uOutFrameID = 0;
    m_uInFrameID = 0;
    m_iWidth = 0;
    m_iHeight = 0;
    m_VideoAppBuffer[0] = NULL;
}

ECode CCamVideo::CreateBuffer(int iWidth, int iHeight)
{
    //Create Max Buffer size once and use it always!
    //
    m_iWidth = iWidth;
    m_iHeight = iHeight;
    m_ulRecordBufFrameLen = iWidth
                         * iHeight
                         * CAMERA_DATA_BYTES;
    kprintf("CCamVideo::Start m_iWidth:%d, m_iHeight%d.\n",
        m_iWidth,
        m_iHeight);

    if(NULL == m_OutFramesBuffer){
        m_OutFramesBuffer = (BYTE *)Nu_DmaBufAlloc(
            (LCD_FRAME_SIZE * USED_RECORD_FREAMS), FALSE);
        if(NULL == m_OutFramesBuffer){
            m_ulRecordBufFrameLen = 0;
            DRV_EXPRESSION(ERRORLEVEL,
                kprintf("m_OutFramesBuffer Nu_DmaBufAlloc failed\n"));
            return E_OUT_OF_MEMORY;
        }
        kprintf("CCamVideo::CreateBuffer OK, addr:0x%x.\n", m_OutFramesBuffer);
    }

    return NOERROR;

}

void CCamVideo::FreeBuffer()
{
    if(NULL != m_OutFramesBuffer){
        kprintf("CCamVideo::FreeBuffer OK. addr:0x%x.\n", m_OutFramesBuffer);
        Nu_DmaBufFree((physaddr_t)m_OutFramesBuffer,
            (LCD_FRAME_SIZE * USED_RECORD_FREAMS));
        m_OutFramesBuffer = NULL;
    }
}

ECode CCamVideo::Initialize(CAMERA_MODE mode)
{
    m_mode = mode;

    return NOERROR;
}

void VideoCallBackFunc(void *pFrameBuffer)
{
    g_CameraDriver.m_pVideo->DoWork(pFrameBuffer);
}

void PreviewCallBackFunc(void *pFrameBuffer)
{
    g_CameraDriver.m_Preview.DoWork(pFrameBuffer);
}

ECode CCamVideo::Start(int iWidth, int iHeight, IEvent ** ppOutEvent)
{
    kprintf("Start.\n");
    int i, iFrameSize;
    ECode ec = DzCreateEventObj(FALSE, UNSIGNALED, &m_pFrameCopyEvent);
    if(FAILED(ec)){
        kprintf("Start DzCreateEventObj failed.\n");
        return ec;
    }
//    kprintf("Create m_pFrameCopyEvent:0x%x.\n", m_pFrameCopyEvent);

    if(NULL == m_OutFramesBuffer){
        ec = CreateBuffer(iWidth, iHeight);
        if(FAILED(ec)){
            DRV_EXPRESSION(ERRORLEVEL,
                kprintf("CCamVideo Start CreateBuffer failed\n"));
            goto ERR;
        }
    }
    iFrameSize = 0;
    for(i = 0; i < USED_RECORD_FREAMS; i++){
        m_VideoAppBuffer[i] = (Uint16 *)&m_OutFramesBuffer[iFrameSize];
        kprintf("CCamVideo mem_appBuffer[%d] 0x%x.\n",i, m_VideoAppBuffer[i]);
        iFrameSize += LCD_FRAME_SIZE;
    }

    m_uOutFrameID = 0;
    m_uInFrameID = 0;
    s_bStopFlag = FALSE;

    CAM_CALL_BACK CallBackFunc;
    if (CAM_MODE_CAPDISP == m_mode) {
        CallBackFunc = VideoCallBackFunc;
    } else {
        CallBackFunc = PreviewCallBackFunc;
    }

    if(nu_CameraStart(m_mode,
        m_VideoAppBuffer,
        (Uint8)USED_RECORD_FREAMS,
        m_iWidth,
        m_iHeight,
        CallBackFunc)){
        DRV_EXPRESSION(ERRORLEVEL,
            kprintf("BeginVideoRecord nu_CameraStart failed\n"));
        ec = E_INVALID_OPERATION;
        goto ERR;
    }

    *ppOutEvent = m_pFrameCopyEvent;
    m_pFrameCopyEvent->AddRef();

    kprintf("Start OK.\n");
    return NOERROR;

ERR:

    if(NULL != m_pFrameCopyEvent){
//        kprintf("Distory m_pFrameCopyEvent:0x%x.\n", m_pFrameCopyEvent);
        m_pFrameCopyEvent->Release();
        m_pFrameCopyEvent = NULL;
    }

    FreeBuffer();

    m_VideoAppBuffer[0] = NULL;

    return ec;
}

void dumpmem(char *pBuffer, int size)
{
    int i;
    for(i = 0; i < size; i++){
        if(0 == i%16){
            kprintf("\n");
        }
        kprintf("0x%x ", pBuffer[i]);
    }
    kprintf("\n");
}

void CCamVideo::DoWork(void *pFrameBuffer)
{
    m_VideoAppBuffer[GET_VIDEOBUFFER_ID(m_uInFrameID)] = (Uint16 *)pFrameBuffer;

    m_uInFrameID++;

    if(NULL != m_pFrameCopyEvent){
        nu_notifyDeviceCaller((Uint32)&(((CEvent *)m_pFrameCopyEvent)->m_kevent), SIGNALED);
    }
}

ECode CCamVideo::Stop()
{
    s_lock.Lock();
    s_bStopFlag = TRUE;
    s_lock.Unlock();

    nu_CameraStop();

    if(NULL != m_pFrameCopyEvent){
//        kprintf("Distory m_pFrameCopyEvent:0x%x.\n", m_pFrameCopyEvent);
        m_pFrameCopyEvent->Release();
        m_pFrameCopyEvent = NULL;
    }

    FreeBuffer();

    m_VideoAppBuffer[0] = NULL;

    kprintf("Stop OK.\n");
    return NOERROR;
}

ECode CCamVideo::GetFrame(Camera_ImageFrame* pFrame)
{
    static Int32 s_n32InvaildBuffer = 0;
    UInt32 uInFrameID;

    InterlockedExchange((PInt32)&uInFrameID, (Int32)m_uInFrameID);

    if (m_uOutFrameID == uInFrameID) {
        if (s_n32InvaildBuffer++ > 10) {
            DzSleep(DzMillisecondsToTicks(10), NULL);
            s_n32InvaildBuffer = 0;
        }
        return E_DOES_NOT_EXIST;
    }

    s_lock.Lock();
    if (s_bStopFlag) {
        s_lock.Unlock();
        return E_DOES_NOT_EXIST;
    }

    Uint16 * pFrameBuffer;
    pFrame->nID = m_uOutFrameID;

    pFrame->FrameSize = m_ulRecordBufFrameLen;
//    kprintf("GetFrame add pFrame : 0x%x. \n", pFrame);

    pFrameBuffer = m_VideoAppBuffer[GET_VIDEOBUFFER_ID(m_uOutFrameID)];

    memcpy(pFrame->pBuffer,
          (void *)pFrameBuffer,
          m_ulRecordBufFrameLen);

    nu_CameraAck(pFrameBuffer);

    s_lock.Unlock();

    m_uOutFrameID++;

    return NOERROR;
}
